import math
import random
import sys
import threading

import pygame
from pygame import Vector3
import requests
from colorama import *
from h3e import *
from function_util import *
import time


class Link:
  """神经连接（突触），单向"""
  def __init__(self, nodeA: 'Node', nodeB: 'Node'):
    self.nodeA = nodeA
    self.nodeB = nodeB
    self.nodeA.sw.allLinks.append(self)
    self.weight = random.uniform(0, 2)
    """权重，权重越大，传播速度越快"""
    self.nodeA.targetLinks[self] = self.nodeB
    self.nodeB.sourceLinks[self] = self.nodeA
    self.activeTime = 0.0
    """突触脉冲时间"""

  def __repr__(self):
    return f'<A={self.nodeA.nid} B={self.nodeB.nid} W={round(self.weight, 8)}>'

  def Destroy(self):
    self.nodeA.sw.allLinks.remove(self)
    self.nodeA.targetLinks.pop(self)
    self.nodeB.sourceLinks.pop(self)


class Node:
  """神经元节点"""
  def __init__(self, sw: 'SW', pos):
    self.pos = pos
    self.sw = sw
    self.nid = len(self.sw.allNodes)
    self.cantActiveTime = 0.0
    """不应期"""
    self.targetLinks = {}
    self.sourceLinks = {}

  def __repr__(self):
    return f'<nid={self.nid} pos~={Round(self.pos)}>'

  def Active(self, value, path: list):
    """激活此神经元"""
    # print('value=', value, self.nid)

    nowTime = time.time()
    if nowTime < self.cantActiveTime:
      return
    self.cantActiveTime = nowTime + 0.1
    path.append(self)
    self.sw.OnActive(self, path)
    for link, node in self.targetLinks.items():
      if link.activeTime > nowTime:
        continue
      distance = Distance(self.pos, node.pos)
      activeValue = value * link.weight * (1.0 / distance)
      # print('activeValue=', activeValue)
      if activeValue < 10e-8:
        continue
      sleep = distance / link.weight * 0.01
      link.activeTime = sleep + nowTime
      node.TryActive(sleep, activeValue, path + [link])

  def TryActive(self, sleep, value, path):
    self.sw.taskList.append((time.time() + sleep, self.Active, value, path))

  def InitLinks(self):
    for node in self.sw.allNodes:
      if random.random() < 0.5 and node != self:
        Link(self, node)


class SW:
  def __init__(self, callBack=None):
    self.allRestrain = False
    self.callBack = callBack
    self.bePath = []
    self.allNodes = []
    self.allLinks = []
    for _ in range(100):
      self.allNodes.append(
        Node(self, (random.uniform(0, 100), random.uniform(0, 100), random.uniform(0, 100)))
      )
    for node in self.allNodes:
      node.InitLinks()
    self.xNodes = []
    self.aNodes = []
    self.taskList = []
    self.lastBe = None
    self.mainThread = threading.Thread(target=self.Run, daemon=True)
    self.mainThread.start()

  def Run(self):
    while 1:
      sortedTaskList = sorted(self.taskList, key=lambda x: x[0])
      nowTime = time.time()
      # if sortedTaskList:
      #   print('sortedTaskList=', len(sortedTaskList))
      for startTime, activeFunc, value, path in sortedTaskList:
        if startTime > nowTime or self.allRestrain:
          break
        activeFunc(value, path)
        self.taskList.remove((startTime, activeFunc, value, path))
      if self.allRestrain:
        self.taskList.clear()
        for link in self.allLinks:
          link.activeTime = 0.0
        self.allRestrain = False

  def Input(self, x, y, a):
    print('Input=', x)
    self.Study(y)
    self.InitXANodes(x, a)
    self.ActiveXNodes(x)

  def Study(self, y):
    if self.lastBe is None:
      return
    print('Study=', y)
    sumLinkCount = (len(self.bePath) - 1) // 2
    linkPathId = 0
    for link in self.bePath:
      if type(link) == Link:
        linkPathId += 1
        pathWeight = 1.0 - linkPathId / sumLinkCount
        link.weight += y * pathWeight  # y=-1.0时，w+=-1.0*1.0=w-=1.0
    # self.bePath.clear()

  def ActiveXNodes(self, x):
    xid = 0
    for node in self.xNodes:
      node.TryActive(0.0, float(x[xid]), [])
      xid += 1

  def OnActive(self, node: Node, path):
    # print('OnActive=', node)
    if node in self.aNodes:
      self.lastBe = self.aNodes.index(node)
      self.bePath = path
      print('行为！', self.lastBe, path)
      self.allRestrain = True
      if self.callBack is not None:
        self.callBack(self.lastBe)

  def InitXANodes(self, x, a):
    """初始化感知神经元"""
    if self.xNodes:
      return
    sortedNodes = sorted(self.allNodes, key=lambda node: Length(node.pos))
    # print('sortedNodes=', sortedNodes)
    self.xNodes = sortedNodes[:len(x)]
    self.aNodes = sortedNodes[-len(a):]
    for node in self.xNodes:
      for link in node.sourceLinks.copy():
        link.Destroy()
    for node in self.aNodes:
      for link in node.targetLinks.copy():
        link.Destroy()

  def Draw(self):
    nowTime = time.time()
    allNodes = sorted(self.allNodes, key=lambda node: Distance(camera_pos, node.pos), reverse=True)
    nodes2d = {}
    drawLinks = set()
    for node in allNodes:
      if node in self.xNodes:
        color = (0, 255, 0)
      elif node in self.aNodes:
        color = (255, 255, 0)
      elif node in self.bePath:
        color = (255, 125, 255)
      else:
        color = (255, 0, 0)
      if node.cantActiveTime > nowTime:
        color = (0, 0, 255)
      if node not in nodes2d:
        nodes2d[node] = GetScreenPos(node.pos)
      if nodes2d[node] is None:
        continue
      dP = nodes2d[node][1]
      color = (int(color[0] * dP), int(color[1] * dP), int(color[2] * dP))
      pygame.draw.circle(screen, color, nodes2d[node][0], 10 * dP, int(10 * dP))
      for link in node.targetLinks:
        if link in drawLinks:
          continue
        drawLinks.add(link)
        if link.nodeA not in nodes2d:
          nodes2d[link.nodeA] = GetScreenPos(link.nodeA.pos)
        if link.nodeB not in nodes2d:
          nodes2d[link.nodeB] = GetScreenPos(link.nodeB.pos)
        if nodes2d[link.nodeA] is None or nodes2d[link.nodeB] is None:
          continue
        if link.activeTime > nowTime:
          color = (255, 125, 125)
        elif link in self.bePath:
          color = (255, 125, 255)
        else:
          color = (125, 125, 255)
        dP = (nodes2d[link.nodeA][1] + nodes2d[link.nodeB][1]) * 0.5
        color = (int(color[0] * dP), int(color[1] * dP), int(color[2] * dP), 10)
        pygame.draw.line(
          screen, color, nodes2d[link.nodeA][0], nodes2d[link.nodeB][0]
        )
    for node in self.bePath:
      if node not in nodes2d or nodes2d[node] is None:
        continue
      pygame.draw.circle(screen, (125, 255, 125), nodes2d[node][0], 5, 1)
      textSurface = textFont.render(f'{node.nid}', True, (125, 255, 125))
      screen.blit(textSurface, nodes2d[node][0])


def GetScreenPos(pos):
  screenPos = W2L(camera_rot, camera_pos, fov, pos)
  if screenPos is None:
    return
  distance = Distance(camera_pos, pos)
  dP = min(1.0, 0.7 / (distance * 0.1) + 0.3)
  return [(screenPos[0] * screenSize[0], screenPos[1] * screenSize[1]), dP]


def Test1():
  rq = requests.post(
    'http://127.0.0.1:5011/add_ai', json={}
  )
  my = SW()
  # my.Load()
  t = 0
  while 1:
    print(Fore.RED + 't=', t, Style.RESET_ALL)
    result = rq.json()
    rq = requests.post(
      'http://127.0.0.1:5011/move', json={'d': my.Input(tuple(result['frame']), result['y'], [_i for _i in range(9)])}
    )
    # input()
    time.sleep(0.05)
    t += 1
    if t > 1000:
      # my.Save()
      return


def handle_movement():
  global move_vector, rot_vector, camera_rot
  key_pressed = pygame.key.get_pressed()
  move_vector = Vector3(0, 0, 0)
  rot_vector = Vector3(0, 0, 0)
  # camera_vector = RotToDir(camera_rot)
  # 相机坐标系下移动
  if key_pressed[pygame.K_w]:
    move_vector.z += 1.0
  elif key_pressed[pygame.K_s]:
    move_vector.z -= 1.0
  elif key_pressed[pygame.K_a]:
    move_vector.x -= 1.0
  elif key_pressed[pygame.K_d]:
    move_vector.x += 1.0
  elif key_pressed[pygame.K_SPACE]:
    move_vector.y -= 1.0
  elif key_pressed[pygame.K_LSHIFT]:
    move_vector.y += 1.0
  elif key_pressed[pygame.K_UP]:
    rot_vector.x -= math.radians(1.0)
  elif key_pressed[pygame.K_DOWN]:
    rot_vector.x += math.radians(1.0)
  elif key_pressed[pygame.K_LEFT]:
    rot_vector.y += math.radians(1.0)
  elif key_pressed[pygame.K_RIGHT]:
    rot_vector.y -= math.radians(1.0)
  elif key_pressed[pygame.K_q]:
    rot_vector.z += math.radians(1.0)
  elif key_pressed[pygame.K_e]:
    rot_vector.z -= math.radians(1.0)
  move_vector = L2W(camera_rot, camera_pos, move_vector)

  # if rot_vector[0] != 0.0 or rot_vector[1] != 0.0 or rot_vector[2] != 0.0:
  #   camera_rot = L2W(camera_rot, camera_pos, rot_vector)
  #   """局部旋转->局部向量->世界向量->世界旋转"""


pygame.init()
pygame.display.set_caption('拟真神网')
screenSize = (600, 600)
screen = pygame.display.set_mode(screenSize)
textFont = pygame.font.Font('simhei.ttf', 16)
camera_pos = Vector3(0, 0, 0)
camera_rot = Vector3(0, 0, 0)
move_vector = Vector3(0, 0, 0)
rot_vector = Vector3(0, 0, 0)
fov = 60  # 视场角度
aspect_ratio = screenSize[0] / screenSize[1]
pos_index = {}

testSeq = [
  [0, 0], 0.0, [0, 1],
  [0, 1], 1.0, [0, 1],
  [1, 0], 1.0, [0, 1],
  [1, 1], 0.0, [0, 1],
]


lastTestInput = None
testCount = 10
inputFinished = False


def Test2():
  global lastTestInput
  y = 0.0
  if sw.lastBe is not None:
    if 2 in lastTestInput and sw.lastBe == 1:
      y = 1.0
    else:
      y = -1.0
  lastTestInput = [random.randint(1, 2), random.randint(1, 2)]
  sw.Input(lastTestInput, y, [0, 1])


def TestCallback(be):
  print('inputFinished', be)
  global inputFinished
  inputFinished = False


sw = SW(TestCallback)
# sw = SW()
if __name__ == '__main__':
  while 1:
    for event in pygame.event.get():
      if event.type == pygame.QUIT:
        exit()
      if event.type == pygame.KEYDOWN and event.key == pygame.K_i:
        print('输入！')
        sw.Input([0, 0, 0, 0, 0, 0, 0, 0], 1.0, [be for be in range(8)])

      if event.type == pygame.KEYDOWN and event.key == pygame.K_o:
        print('输入！')
        sw.Input([1, 0, 0, 0, 0, 0, 0, 0], 1.0, [be for be in range(8)])

      if event.type == pygame.KEYDOWN and event.key == pygame.K_p:
        print('输入！')
        sw.Input([1, 1, 1, 1, 1, 1, 1, 1], 1.0, [be for be in range(8)])

      if event.type == pygame.KEYDOWN and event.key == pygame.K_t:
        print('测试！')
        inputFinished = False
        testCount = 10

      if event.type == pygame.KEYDOWN and event.key == pygame.K_y:
        print('测试！')
        inputFinished = False
        testCount = 100

    handle_movement()
    if testCount > 0 and not inputFinished:
      testCount -= 1
      inputFinished = True
      Test2()
    camera_pos += move_vector
    camera_rot += rot_vector
    screen.fill((0, 0, 0))
    pos_index.clear()
    sw.Draw()
    pygame.display.flip()
